/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * =======================================================================
 *
 * Server side user (player entity) moving.
 *
 * =======================================================================
 */

#include "header/server.h"

#define MAX_STRINGCMDS  8

edict_t *sv_player;

void
SV_BeginDemoserver ( void )
{
	char name [ MAX_OSPATH ];

	Com_sprintf( name, sizeof ( name ), "demos/%s", sv.name );
	FS_FOpenFile( name, (fileHandle_t *) &sv.demofile, FS_READ );

	if ( !sv.demofile )
	{
		Com_Error( ERR_DROP, "Couldn't open %s\n", name );
	}
}

/*
 * Sends the first message from the server to a connected client.
 * This will be sent on the initial connection and upon each server load.
 */
void
SV_New_f ( void )
{
	static char *gamedir;
	int playernum;
	edict_t     *ent;

	Com_DPrintf( "New() from %s\n", sv_client->name );

	if ( sv_client->state != cs_connected )
	{
		Com_Printf( "New not valid -- already spawned\n" );
		return;
	}

	/* demo servers just dump the file message */
	if ( sv.state == ss_demo )
	{
		SV_BeginDemoserver();
		return;
	}

	/* serverdata needs to go over for all types of servers 
	   to make sure the protocol is right, and to set the gamedir */
	gamedir = (char *) Cvar_VariableString( "gamedir" );

	/* send the serverdata */
	MSG_WriteByte( &sv_client->netchan.message, svc_serverdata );
	MSG_WriteLong( &sv_client->netchan.message, PROTOCOL_VERSION );
	MSG_WriteLong( &sv_client->netchan.message, svs.spawncount );
	MSG_WriteByte( &sv_client->netchan.message, sv.attractloop );
	MSG_WriteString( &sv_client->netchan.message, gamedir );

	if ( ( sv.state == ss_cinematic ) || ( sv.state == ss_pic ) )
	{
		playernum = -1;
	}
	else
	{
		playernum = sv_client - svs.clients;
	}

	MSG_WriteShort( &sv_client->netchan.message, playernum );

	/* send full levelname */
	MSG_WriteString( &sv_client->netchan.message, sv.configstrings [ CS_NAME ] );

	/* game server */
	if ( sv.state == ss_game )
	{
		/* set up the entity for the client */
		ent = EDICT_NUM( playernum + 1 );
		ent->s.number = playernum + 1;
		sv_client->edict = ent;
		memset( &sv_client->lastcmd, 0, sizeof ( sv_client->lastcmd ) );

		/* begin fetching configstrings */
		MSG_WriteByte( &sv_client->netchan.message, svc_stufftext );
		MSG_WriteString( &sv_client->netchan.message, va( "cmd configstrings %i 0\n", svs.spawncount ) );
	}
}

void
SV_Configstrings_f ( void )
{
	int start;

	Com_DPrintf( "Configstrings() from %s\n", sv_client->name );

	if ( sv_client->state != cs_connected )
	{
		Com_Printf( "configstrings not valid -- already spawned\n" );
		return;
	}

	/* handle the case of a level changing while a client was connecting */
	if ( atoi( Cmd_Argv( 1 ) ) != svs.spawncount )
	{
		Com_Printf( "SV_Configstrings_f from different level\n" );
		SV_New_f();
		return;
	}

	start = atoi( Cmd_Argv( 2 ) );

	/* write a packet full of data */
	while ( sv_client->netchan.message.cursize < MAX_MSGLEN / 2 && start < MAX_CONFIGSTRINGS )
	{
		if ( sv.configstrings [ start ] [ 0 ] )
		{
			MSG_WriteByte( &sv_client->netchan.message, svc_configstring );
			MSG_WriteShort( &sv_client->netchan.message, start );
			MSG_WriteString( &sv_client->netchan.message, sv.configstrings [ start ] );
		}

		start++;
	}

	/* send next command */
	if ( start == MAX_CONFIGSTRINGS )
	{
		MSG_WriteByte( &sv_client->netchan.message, svc_stufftext );
		MSG_WriteString( &sv_client->netchan.message, va( "cmd baselines %i 0\n", svs.spawncount ) );
	}
	else
	{
		MSG_WriteByte( &sv_client->netchan.message, svc_stufftext );
		MSG_WriteString( &sv_client->netchan.message, va( "cmd configstrings %i %i\n", svs.spawncount, start ) );
	}
}

void
SV_Baselines_f ( void )
{
	int start;
	entity_state_t nullstate;
	entity_state_t  *base;

	Com_DPrintf( "Baselines() from %s\n", sv_client->name );

	if ( sv_client->state != cs_connected )
	{
		Com_Printf( "baselines not valid -- already spawned\n" );
		return;
	}

	/* handle the case of a level changing while a client was connecting */
	if ( atoi( Cmd_Argv( 1 ) ) != svs.spawncount )
	{
		Com_Printf( "SV_Baselines_f from different level\n" );
		SV_New_f();
		return;
	}

	start = atoi( Cmd_Argv( 2 ) );

	memset( &nullstate, 0, sizeof ( nullstate ) );

	/* write a packet full of data */
	while ( sv_client->netchan.message.cursize <  MAX_MSGLEN / 2 &&
			start < MAX_EDICTS )
	{
		base = &sv.baselines [ start ];

		if ( base->modelindex || base->sound || base->effects )
		{
			MSG_WriteByte( &sv_client->netchan.message, svc_spawnbaseline );
			MSG_WriteDeltaEntity( &nullstate, base, &sv_client->netchan.message, true, true );
		}

		start++;
	}

	/* send next command */
	if ( start == MAX_EDICTS )
	{
		MSG_WriteByte( &sv_client->netchan.message, svc_stufftext );
		MSG_WriteString( &sv_client->netchan.message, va( "precache %i\n", svs.spawncount ) );
	}
	else
	{
		MSG_WriteByte( &sv_client->netchan.message, svc_stufftext );
		MSG_WriteString( &sv_client->netchan.message, va( "cmd baselines %i %i\n", svs.spawncount, start ) );
	}
}

void
SV_Begin_f ( void )
{
	Com_DPrintf( "Begin() from %s\n", sv_client->name );

	/* handle the case of a level changing while a client was connecting */
	if ( atoi( Cmd_Argv( 1 ) ) != svs.spawncount )
	{
		Com_Printf( "SV_Begin_f from different level\n" );
		SV_New_f();
		return;
	}

	sv_client->state = cs_spawned;

	/* call the game begin function */
	ge->ClientBegin( sv_player );

	Cbuf_InsertFromDefer();
}

void
SV_NextDownload_f ( void )
{
	int r;
	int percent;
	int size;

	if ( !sv_client->download )
	{
		return;
	}

	r = sv_client->downloadsize - sv_client->downloadcount;

	if ( r > 1024 )
	{
		r = 1024;
	}

	MSG_WriteByte( &sv_client->netchan.message, svc_download );
	MSG_WriteShort( &sv_client->netchan.message, r );

	sv_client->downloadcount += r;
	size = sv_client->downloadsize;

	if ( !size )
	{
		size = 1;
	}

	percent = sv_client->downloadcount * 100 / size;
	MSG_WriteByte( &sv_client->netchan.message, percent );
	SZ_Write( &sv_client->netchan.message,
			sv_client->download + sv_client->downloadcount - r, r );

	if ( sv_client->downloadcount != sv_client->downloadsize )
	{
		return;
	}

	FS_FreeFile( sv_client->download );
	sv_client->download = NULL;
}

void
SV_BeginDownload_f ( void )
{
	char    *name;
	extern cvar_t *allow_download;
	extern cvar_t *allow_download_players;
	extern cvar_t *allow_download_models;
	extern cvar_t *allow_download_sounds;
	extern cvar_t *allow_download_maps;
	extern int file_from_pak;
	int offset = 0;

	name = Cmd_Argv( 1 );

	if ( Cmd_Argc() > 2 )
	{
		offset = atoi( Cmd_Argv( 2 ) ); /* downloaded offset */
	}

	/* hacked by zoid to allow more conrol over download
	   first off, no .. or global allow check */
	if ( strstr( name, ".." ) || strstr( name, "\\" ) || !allow_download->value
		 /* leading dot is no good */
		 || ( *name == '.' )
		 /* leading slash bad as well, must be in subdir */
		 || ( *name == '/' )
		 /* next up, skin check */
		 || ( ( strncmp( name, "players/", 6 ) == 0 ) && !allow_download_players->value )
		 /* now models */
		 || ( ( strncmp( name, "models/", 6 ) == 0 ) && !allow_download_models->value )
		 /* now sounds */
		 || ( ( strncmp( name, "sound/", 6 ) == 0 ) && !allow_download_sounds->value )
		 /* now maps (note special case for maps, must not be in pak) */
		 || ( ( strncmp( name, "maps/", 6 ) == 0 ) && !allow_download_maps->value )
		 /* MUST be in a subdirectory */
		 || !strstr( name, "/" ) )
	{   
		/* don't allow anything with .. path */
		MSG_WriteByte( &sv_client->netchan.message, svc_download );
		MSG_WriteShort( &sv_client->netchan.message, -1 );
		MSG_WriteByte( &sv_client->netchan.message, 0 );
		return;
	}

	if ( sv_client->download )
	{
		FS_FreeFile( sv_client->download );
	}

	sv_client->downloadsize = FS_LoadFile( name, (void **) &sv_client->download );
	sv_client->downloadcount = offset;

	if ( offset > sv_client->downloadsize )
	{
		sv_client->downloadcount = sv_client->downloadsize;
	}

	if ( !sv_client->download || ( ( strncmp( name, "maps/", 5 ) == 0 ) && file_from_pak ) )
	{
		Com_DPrintf( "Couldn't download %s to %s\n", name, sv_client->name );

		if ( sv_client->download )
		{
			FS_FreeFile( sv_client->download );
			sv_client->download = NULL;
		}

		MSG_WriteByte( &sv_client->netchan.message, svc_download );
		MSG_WriteShort( &sv_client->netchan.message, -1 );
		MSG_WriteByte( &sv_client->netchan.message, 0 );
		return;
	}

	SV_NextDownload_f();
	Com_DPrintf( "Downloading %s to %s\n", name, sv_client->name );
}

/*
 * The client is going to disconnect, so remove the connection immediately
 */
void
SV_Disconnect_f ( void )
{
	SV_DropClient( sv_client );
}

/*
 * Dumps the serverinfo info string
 */
void
SV_ShowServerinfo_f ( void )
{
	Info_Print( Cvar_Serverinfo() );
}

void
SV_Nextserver ( void )
{
	const char  *v;

	if ( ( sv.state == ss_game ) || ( ( sv.state == ss_pic ) && !Cvar_VariableValue( "coop" ) ) )
	{
		return; /* can't nextserver while playing a normal game */
	}

	svs.spawncount++; /* make sure another doesn't sneak in */
	v = Cvar_VariableString( "nextserver" );

	if ( !v [ 0 ] )
	{
		Cbuf_AddText( "killserver\n" );
	}
	else
	{
		Cbuf_AddText( (char *) v );
		Cbuf_AddText( "\n" );
	}

	Cvar_Set( "nextserver", "" );
}

/*
 * A cinematic has completed or been aborted by a client, so move
 * to the next server,
 */
void
SV_Nextserver_f ( void )
{
	if ( atoi( Cmd_Argv( 1 ) ) != svs.spawncount )
	{
		Com_DPrintf( "Nextserver() from wrong level, from %s\n", sv_client->name );
		return; /* leftover from last server */
	}

	Com_DPrintf( "Nextserver() from %s\n", sv_client->name );

	SV_Nextserver();
}

typedef struct
{
	char    *name;
	void ( *func )( void );
} ucmd_t;

ucmd_t ucmds[] = {
	/* auto issued */
	{ "new", SV_New_f },
	{ "configstrings", SV_Configstrings_f },
	{ "baselines", SV_Baselines_f },
	{ "begin", SV_Begin_f },

	{ "nextserver", SV_Nextserver_f },

	{ "disconnect", SV_Disconnect_f },

	/* issued by hand at client consoles */
	{ "info", SV_ShowServerinfo_f },

	{ "download", SV_BeginDownload_f },
	{ "nextdl", SV_NextDownload_f },

	{ NULL, NULL }
};

void
SV_ExecuteUserCommand ( char *s )
{
	ucmd_t  *u;

	/* Security Fix... This is being set to false so that client's can't
	   macro expand variables on the server.  It seems unlikely that a
	   client ever ought to need to be able to do this... */
	Cmd_TokenizeString( s, false );
	sv_player = sv_client->edict;

	for ( u = ucmds; u->name; u++ )
	{
		if ( !strcmp( Cmd_Argv( 0 ), u->name ) )
		{
			u->func();
			break;
		}
	}

	if ( !u->name && ( sv.state == ss_game ) )
	{
		ge->ClientCommand( sv_player );
	}
}

void
SV_ClientThink ( client_t *cl, usercmd_t *cmd )

{
	cl->commandMsec -= cmd->msec;

	if ( ( cl->commandMsec < 0 ) && sv_enforcetime->value )
	{
		Com_DPrintf( "commandMsec underflow from %s\n", cl->name );
		return;
	}

	ge->ClientThink( cl->edict, cmd );
}

/*
 * The current net_message is parsed for the given client
 */
void
SV_ExecuteClientMessage ( client_t *cl )
{
	int c;
	char    *s;

	usercmd_t nullcmd;
	usercmd_t oldest, oldcmd, newcmd;
	int net_drop;
	int stringCmdCount;
	int checksum, calculatedChecksum;
	int checksumIndex;
	qboolean move_issued;
	int lastframe;

	sv_client = cl;
	sv_player = sv_client->edict;

	/* only allow one move command */
	move_issued = false;
	stringCmdCount = 0;

	while ( 1 )
	{
		if ( net_message.readcount > net_message.cursize )
		{
			Com_Printf( "SV_ReadClientMessage: badread\n" );
			SV_DropClient( cl );
			return;
		}

		c = MSG_ReadByte( &net_message );

		if ( c == -1 )
		{
			break;
		}

		switch ( c )
		{
			default:
				Com_Printf( "SV_ReadClientMessage: unknown command char\n" );
				SV_DropClient( cl );
				return;

			case clc_nop:
				break;

			case clc_userinfo:
				strncpy( cl->userinfo, MSG_ReadString( &net_message ), sizeof ( cl->userinfo ) - 1 );
				SV_UserinfoChanged( cl );
				break;

			case clc_move:

				if ( move_issued )
				{
					return; /* someone is trying to cheat... */
				}

				move_issued = true;
				checksumIndex = net_message.readcount;
				checksum = MSG_ReadByte( &net_message );
				lastframe = MSG_ReadLong( &net_message );

				if ( lastframe != cl->lastframe )
				{
					cl->lastframe = lastframe;

					if ( cl->lastframe > 0 )
					{
						cl->frame_latency [ cl->lastframe & ( LATENCY_COUNTS - 1 ) ] =
							svs.realtime - cl->frames [ cl->lastframe & UPDATE_MASK ].senttime;
					}
				}

				memset( &nullcmd, 0, sizeof ( nullcmd ) );
				MSG_ReadDeltaUsercmd( &net_message, &nullcmd, &oldest );
				MSG_ReadDeltaUsercmd( &net_message, &oldest, &oldcmd );
				MSG_ReadDeltaUsercmd( &net_message, &oldcmd, &newcmd );

				if ( cl->state != cs_spawned )
				{
					cl->lastframe = -1;
					break;
				}

				/* if the checksum fails, ignore the rest of the packet */
				calculatedChecksum = COM_BlockSequenceCRCByte(
					net_message.data + checksumIndex + 1,
					net_message.readcount - checksumIndex - 1,
					cl->netchan.incoming_sequence );

				if ( calculatedChecksum != checksum )
				{
					Com_DPrintf( "Failed command checksum for %s (%d != %d)/%d\n",
							cl->name, calculatedChecksum, checksum,
							cl->netchan.incoming_sequence );
					return;
				}

				if ( !sv_paused->value )
				{
					net_drop = cl->netchan.dropped;

					if ( net_drop < 20 )
					{
						while ( net_drop > 2 )
						{
							SV_ClientThink( cl, &cl->lastcmd );

							net_drop--;
						}

						if ( net_drop > 1 )
						{
							SV_ClientThink( cl, &oldest );
						}

						if ( net_drop > 0 )
						{
							SV_ClientThink( cl, &oldcmd );
						}
					}

					SV_ClientThink( cl, &newcmd );
				}

				cl->lastcmd = newcmd;
				break;

			case clc_stringcmd:
				s = MSG_ReadString( &net_message );

				/* malicious users may try using too many string commands */
				if ( ++stringCmdCount < MAX_STRINGCMDS )
				{
					SV_ExecuteUserCommand( s );
				}

				if ( cl->state == cs_zombie )
				{
					return; /* disconnect command */
				}

				break;
		}
	}
}

